#pragma once
#include "frpc_client.h"
#include <cppbase/utility.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>

namespace frpc
{

	CommMessageOut* ClientTask::message_out()
	{
		long long seqid = this->get_seq();
		return this->WFComplexClientTask::message_out();
		if (seqid == 0)
		{
			DLOG("prepare to send username");
			_is_user_request = false;
			auto msg = new Request;
			auto user = AppGlobal::instance()->get_config("username");
			if (user.size() == 0)
				user = "guest";
			//Args arg;
			//arg.add_arg_string(user);
			//arg.add_arg_int32(1);
			//msg->set_cmd_output(Command::HandShake, std::move(user)); //arg.SerializeAsString());
			return msg;
		}
		if (seqid == 1)
		{
			DLOG("prepare to send hashed pw");
			_is_user_request = false;
			auto msg = new Request;
			Context* ctx = (Context*)this->get_connection()->get_context();
			auto pw = AppGlobal::instance()->get_config("password");
			SHA256_CTX context;
			std::string hash_r, hash_r2;
			hash_r.resize(32);
			hash_r2.resize(32);
			SHA256_Init(&context);
			SHA256_Update(&context, (const unsigned char*)pw.data(), pw.size());
			// SHA256_Update(&context, (unsigned char *)ctx->sec_salt.data(), ctx->sec_salt.size());
			SHA256_Final((unsigned char*)hash_r.data(), &context);
			SHA256_Init(&context);
			SHA256_Update(&context, (const unsigned char*)hash_r.data(), hash_r.size());
			SHA256_Final((unsigned char*)hash_r2.data(), &context);
			// Args arg;
			// arg.add_arg_string(std::move(hash_r2));
		   // msg->set_cmd_output(Command::LoginUser, std::move(hash_r2));
			return msg;
		}

		return this->WFComplexClientTask::message_out();
	}

	CommMessageIn* ClientTask::message_in()
	{
		return this->WFComplexClientTask::message_in();
		long long seqid = this->get_seq();
		//to build a handshake msg
		if (seqid < 2) {
			_is_user_request = false;
			return new Reply;
		}

	}

	void ClientTask::set_compress_type(CompressType type)
	{
	}

	bool ClientTask::check_request()
	{
		return true;
	}
	//will be called after msg append. before call task callback
	//return 0 means close socket
	//to decide cmd result status
	int ClientTask::keep_alive_timeout()
	{
		auto seqid = this->get_seq();
		_state = WFT_STATE_SUCCESS;


		return this->keep_alive_timeo;


		//error_ = 0;
		auto* resp = (Request*)this->get_message_in();
		if (resp->bad_input())
		{
			_state = WFT_STATE_TASK_ERROR;
			return 0;
		}
		if (seqid < 2)
		{
			if (seqid == 0)
			{
				DLOG("set connection context with security salt.");
				// auto *ctx = new Context;
				// auto *conn = this->get_connection();
			   //  ctx->sec_salt = resp->input();
			   //  conn->set_context(ctx, [](void *ctx)
			  //                     { delete (Context *)ctx; });
			}
			else
			{
				DLOG("user login success.");
			}
		}

		return this->keep_alive_timeo; // in WFNetworkTask
	}
	//called after keep_alive_timeout
	bool ClientTask::finish_once()
	{
		DLOG("call finish_once.");
		if (_is_user_request)
			return true;

		DLOG("not user request ,so delete out-in msg.");
		delete this->get_message_out();
		delete this->get_message_in();

		if (this->state == WFT_STATE_SUCCESS && _state != WFT_STATE_SUCCESS)
		{
			this->state = _state;
			this->disable_retry();
		}
		_is_user_request = true;
		return false; //tell parent(WFComplexClientTask::done() ) don't call user callback
	}


	static inline bool addr_to_string(const struct sockaddr* addr,
		char* ip_str, socklen_t len,
		unsigned short* port)
	{
		const char* ret = NULL;

		if (addr->sa_family == AF_INET)
		{
			struct sockaddr_in* sin = (struct sockaddr_in*)addr;

			ret = inet_ntop(AF_INET, &sin->sin_addr, ip_str, len);
			*port = ntohs(sin->sin_port);
		}
		else if (addr->sa_family == AF_INET6)
		{
			struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr;

			ret = inet_ntop(AF_INET6, &sin6->sin6_addr, ip_str, len);
			*port = ntohs(sin6->sin6_port);
		}
		else
			errno = EINVAL;

		return ret != NULL;
	}

	bool ClientTask::get_remote(std::string& ip, unsigned short* port) const
	{
		struct sockaddr_storage addr;
		socklen_t addrlen = sizeof(addr);

		ip.resize(INET6_ADDRSTRLEN + 1);

		if (this->get_peer_addr((struct sockaddr*)&addr, &addrlen) == 0)
			return addr_to_string((struct sockaddr*)&addr, (char*)ip.c_str(),
				INET6_ADDRSTRLEN + 1, port);

		return false;
	}


	//static void set_ip_port(std::string host,uint16_t port){	}
	//static New()

	ClientTask::ClientTask(const std::string& service_name,
		const std::string& method_name, RPCTaskParams* opt, std::function<int(int, RPCWorker&)>&& callback) :
		WFComplexClientTask(0, nullptr), _user_done(callback), _is_user_request(true), _state(0)
	{/*
		char buf[8];
		std::string url = "scheme://";
		ParsedURI uri;
		sprintf(buf, "%u", 8000);
		url += "192.168.106.128";
		url += ":";
		url += buf;
		URIParser::parse(url, uri);
		init(std::move(uri));
		*/
		// if (!opt)

		 //set_transport_type(TT_TCP);
		 //set_fixed_addr(true);
		this->set_send_timeout(opt->send_timeout);
		this->set_receive_timeout(opt->receive_timeout);
		//watch_timeout_ = opt->watch_timeout;
		this->set_keep_alive(opt->keep_alive_timeout);
		retry_max_ = opt->retry_max;


		this->req.set_compress_type(opt->compress_type);

		this->req.set_service_name(service_name);
		this->req.set_method_name(method_name);
		set_callback(std::bind(&ClientTask::rpc_callback, this, std::placeholders::_1));
	}

	void ClientTask::rpc_callback(WFNetworkTask<Request, Reply>* task)
	{
		RPCWorker worker(new Context(this),
			&this->req, &this->resp);
	/*	int status_code = this->resp.get_status_code();

		if (status_code != 0)
		{
			this->state = WFT_STATE_TASK_ERROR;
			this->error = status_code;
		}
		*/ if (this->state == WFT_STATE_SUCCESS)
		{
			int status_code = this->resp.decompress();
			if (status_code == 0)
			{
				this->resp.set_status_code(0);
				status_code = _user_done(status_code, worker);
				if (status_code == 0)
					return;
			}
			else {
				this->state = WFT_STATE_TASK_ERROR;
				DLOG("task decompress msg failed");
				//this->error = status_code;
			}


		}
		else {
			DLOG("task->state error:%d,connection refused?", this->state);
		}

		
	}

	static int __get_addr_info(const std::string& host, unsigned short port,
		struct sockaddr_storage* ss, socklen_t* ss_len)
	{
		if (!host.empty())
		{
			char front = host.front();
			char back = host.back();

			if (host.find(':') != std::string::npos)
			{
				struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ss;

				memset(sin6, 0, sizeof(struct sockaddr_in6));
				if (inet_pton(AF_INET6, host.c_str(), &sin6->sin6_addr) == 1)
				{
					sin6->sin6_family = AF_INET6;
					sin6->sin6_port = htons(port);
					*ss_len = sizeof(struct sockaddr_in6);
					return 0;
				}
			}
			else if (isdigit(back) && isdigit(front))
			{
				struct sockaddr_in* sin = (struct sockaddr_in*)ss;

				memset(sin, 0, sizeof(struct sockaddr_in));
				if (inet_pton(AF_INET, host.c_str(), &sin->sin_addr) == 1)
				{
					sin->sin_family = AF_INET;
					sin->sin_port = htons(port);
					*ss_len = sizeof(struct sockaddr_in);
					return 0;
				}
			}
		}

		return -1;
	}

	bool client_init(RPCClientParams& params, ParsedURI& uri,
		struct sockaddr_storage* ss, socklen_t* ss_len)
	{
		if (!params.host.empty())
		{
			if (__get_addr_info(params.host, params.port, ss, ss_len) == 0)
				return true;

			//    if (params.is_ssl)
			//        uri.scheme = strdup(SRPC_SSL_SCHEME);
			//    else
			        uri.scheme = strdup("scheme");

			uri.host = strdup(params.host.c_str());
			uri.port = strdup(std::to_string(params.port).c_str());
			if (uri.scheme && uri.host && uri.port)
				uri.state = URI_STATE_SUCCESS;
			else
			{
				uri.state = URI_STATE_ERROR;
				uri.error = errno;
			}
		}
		else
		{
			URIParser::parse(params.url, uri);
			params.is_ssl = (uri.scheme &&
				(strcmp(uri.scheme, "https") == 0 ||
					strcmp(uri.scheme, "srpcs") == 0));
		}

		return false;
	}
	void Client::init(const RPCClientParams* params) {

		this->params = *params;

		this->_has_addr_info = client_init(this->params, this->uri, &this->ss, &this->ss_len);
	}
	void Client::task_init(ClientTask* task) {
		if (this->_has_addr_info)
			task->init(this->params.is_ssl ? TT_TCP_SSL : TT_TCP,
				(const struct sockaddr*)&this->ss, this->ss_len, "");
		else
		{
			task->init(this->uri);
			task->set_transport_type(this->params.is_ssl ? TT_TCP_SSL : TT_TCP);
		}
		task->get_req()->set_correlation_id(uint64_t(task));
	}
}